package EA.testproblems;
import EA.*;
import EA.recording.*;
import RKUjava.util.RKUStringUtils;
import java.util.Hashtable;

/** 
    The purpose of this class is to model the concept of a numerical problem. 
    It is supposed to be subclassed into the problems your EA attempt to 
    attack.
    It contains the relevant informations about a numerical problems such as
    objective function, known maxima and minima, etc. 
    
    
*/
public class NumericalProblem
{
    
  /** The name of this problem. */
  public String name;
    
  /** The fitness function. */
  public NumericalFitness objectivefunction;

  /** The number of dimensions this problem have. */
  public int dimensions;

  /** The intervals for each of the parameters. */
  public Interval intervals[];
    
  /** Is this problem a maximization problem. */
  public boolean ismaximization;

  /** The known maxima for this problem. */
  public NumericalOptimum[] knownmaxima;

  /** The known manima for this problem. */
  public NumericalOptimum[] knownminima;

  /** Optima radius. How close to an optima should a point be before it
      is considered near that optima. */
  public double optimumradius;

  public NumericalProblem()
    {
      super();
    }

  /** Get the fitness. <br>
      Default implementation: Call objectivefuction.calcFitness()
  */
  public double getFitness(double[] realpos)
    {
      return objectivefunction.calcFitness(realpos);
    }

  /** Get the global optimum. If the problem is a maximization problem
      then the global maximum is returned otherwise the global minimum
      is returned.
  */
  public NumericalOptimum getGlobalOptimum()
    {
      NumericalOptimum res = null;
      double bestfit, curfit;
      int i;

      if (ismaximization) {
	bestfit = Double.NEGATIVE_INFINITY;
	for (i = 0;i<knownmaxima.length;i++) {
	  if ((curfit = knownmaxima[i].getFitness())>bestfit) {
	    bestfit = curfit;
	    res = knownmaxima[i];
	  }
	}
      }
      else {
	bestfit = Double.POSITIVE_INFINITY;
	for (i = 0;i<knownminima.length;i++) {
	  if ((curfit = knownminima[i].getFitness())<bestfit) {
	    bestfit = curfit;
	    res = knownminima[i];
	  }
	}
      }
      return res;
    }


  /** Get the global maximum. 
  */
  public NumericalOptimum getGlobalMaximum()
    {
      NumericalOptimum res = null;
      double bestfit, curfit;
      int i;

      bestfit = Double.NEGATIVE_INFINITY;
      for (i = 0;i<knownmaxima.length;i++) {
	if ((curfit = knownmaxima[i].getFitness())>bestfit) {
	  bestfit = curfit;
	  res = knownmaxima[i];
	  }
      }
      return res;
    }

  /** Get the global minimum. 
  */
  public NumericalOptimum getGlobalMinimum()
    {
      NumericalOptimum res = null;
      double bestfit, curfit;
      int i;

      bestfit = Double.POSITIVE_INFINITY;
      for (i = 0;i<knownminima.length;i++) {
	if ((curfit = knownminima[i].getFitness())<bestfit) {
	  bestfit = curfit;
	  res = knownminima[i];
	  }
      }
      return res;
    }

  /** Get the closest optimum within range of <tt>optimumradius</tt>. 
      @return The optimum closest to realpos or null if all optima are more
      than <tt>optimumradius</tt> away from realpos.  */
  public NumericalOptimum getClosestFoundOptimum(double[] realpos)
    {
      NumericalOptimum res = null;
      double bestdistance = Double.POSITIVE_INFINITY;
      double curdistance;
      int i;

      for (i = 0;i<knownmaxima.length;i++) {
	curdistance = knownmaxima[i].distanceToOptimum(realpos);
	if (optimumradius>curdistance && bestdistance>curdistance) {
	  res = knownmaxima[i];
	  bestdistance = curdistance;
	}
      }

      for (i = 0;i<knownminima.length;i++) {
	curdistance = knownminima[i].distanceToOptimum(realpos);
	if (optimumradius>curdistance && bestdistance>curdistance) {
	  res = knownminima[i];
	  bestdistance = curdistance;
	}
      }
      return res;
    }

  /** Get the closest optimum.
      @return The optimum closes to realpos or null if no optima are known.
  */
  public NumericalOptimum getClosestOptimum(double[] realpos)
    {
      NumericalOptimum res = null;
      double bestdistance = Double.POSITIVE_INFINITY;
      double curdistance;
      int i;

      for (i = 0;i<knownmaxima.length;i++) {
	curdistance = knownmaxima[i].distanceToOptimum(realpos);
	if (bestdistance>curdistance) {
	  res = knownmaxima[i];
	  bestdistance = curdistance;
	};
      };

      for (i = 0;i<knownminima.length;i++) {
	curdistance = knownminima[i].distanceToOptimum(realpos);
	if (bestdistance>curdistance) {
	  res = knownminima[i];
	  bestdistance = curdistance;
	};
      };

      return res;
    }

  /** Get the closest maximum within range of <tt>optimumradius</tt>. 
      @return The maximum closest to realpos or null if all optima are more
      than <tt>optimumradius</tt> away from realpos.
  */
  public NumericalOptimum getClosestFoundMaximum(double[] realpos)
    {
      NumericalOptimum res = null;
      double bestdistance = Double.POSITIVE_INFINITY;
      double curdistance;
      int i;

      for (i = 0;i<knownmaxima.length;i++) {
	curdistance = knownmaxima[i].distanceToOptimum(realpos);
	if (optimumradius>curdistance && bestdistance>curdistance) {
	  res = knownmaxima[i];
	  bestdistance = curdistance;
	}
      }

      return res;
    }

  /** Get the closest maximum.
      @return The maximum closes to realpos or null if no maxima are known.
  */
  public NumericalOptimum getClosestMaximum(double[] realpos)
    {
      NumericalOptimum res = null;
      double bestdistance = Double.POSITIVE_INFINITY;
      double curdistance;
      int i;

      for (i = 0;i<knownmaxima.length;i++) {
	curdistance = knownmaxima[i].distanceToOptimum(realpos);
	if (bestdistance>curdistance) {
	  res = knownmaxima[i];
	  bestdistance = curdistance;
	};
      };

      return res;
    }

  /** Get the closest minimum within range of <tt>optimumradius</tt>. 
      @return The minimum closest to realpos or null if all optima are more
      than <tt>optimumradius</tt> away from realpos.
  */
  public NumericalOptimum getClosestFoundMinimum(double[] realpos)
    {
      NumericalOptimum res = null;
      double bestdistance = Double.POSITIVE_INFINITY;
      double curdistance;
      int i;

      for (i = 0;i<knownminima.length;i++) {
	curdistance = knownminima[i].distanceToOptimum(realpos);
	if (optimumradius>curdistance && bestdistance>curdistance) {
	  res = knownminima[i];
	  bestdistance = curdistance;
	}
      }

      return res;
    }

  /** Get the closest minimum.
      @return The minimum closes to realpos or null if no minima are known.
  */
  public NumericalOptimum getClosestMinimum(double[] realpos)
    {
      NumericalOptimum res = null;
      double bestdistance = Double.POSITIVE_INFINITY;
      double curdistance;
      int i;

      for (i = 0;i<knownminima.length;i++) {
	curdistance = knownminima[i].distanceToOptimum(realpos);
	if (bestdistance>curdistance) {
	  res = knownminima[i];
	  bestdistance = curdistance;
	};
      };

      return res;
    }

  /** Get an array of SequenceTableColumns representing the 
      dimensions in this problem.
      @param prependtext A string that will be prepended to the names of the
      variables.
      @return An array of SequenceTableColumns equal to the dimensionality.
      @see EA.recording.SequenceTableColumn
  */
  public SequenceTableColumn[] getSequenceTableColumns(String prependtext)
    {
      SequenceTableColumn[] res = new SequenceTableColumn[dimensions];
      for (int i = 0;i<dimensions;i++) {
	res[i] = new SequenceTableColumn(prependtext+"x"+(i+1));
      }
      return res;
    }

  /** Set a countlistener for this problem. (All it does is calling
      the setCountListener in its fitnessfunction.) 
      @see EA.Fitness#setCountListener
  */
  public void setCountListener(FitEvalCountListener listener, int samplerate) 
    {
      objectivefunction.setCountListener(listener,samplerate);
    }

  /** Reset the problem. This will call resetProblem in the objective function.
      You should call this every time you restart your algorithm.
  */
  public final void resetProblem()
    {
      objectivefunction.resetFunction();
      NumericalProblem_resetProblem_inner();
    }

  /** Override this in your subclass with your resetting code. */
  public void NumericalProblem_resetProblem_inner()
    {
    }

  /** Set the settings for this problem. 
      <b>Note:</b> This method must be overridden in your subclass.
      @param settings A hashtable containing the settings for this problem.
      @param framework The framework this problem is applied in. Often
      needed if dimensions and are set from the settingsfile. null may be
      passed if no framework exists.
  */
  public void setSettings(Hashtable settings, NumericalFramework framework)
    {
    }

  /** Display the settings this problem supports. The settings will be 
      returned in the BatchJobScheduler compatible inputformat.<br>
      <b>Note:</b> This method must be overridden in your subclass.
      @return A string representing the possible settings. 
      @see EA.BatchJobScheduler
  */
  public String getDefaultSettings()
    {
      return "";
    }
}
